package org.dwpd.service.message.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;
import org.dwpd.domain.common.Code;
import org.dwpd.domain.common.PageResult;
import org.dwpd.domain.common.Result;
import org.dwpd.domain.message.RequestParams;
import org.dwpd.mapper.message.SearchMapper;
import org.dwpd.domain.message.Message;
import org.dwpd.domain.message.MessageEsInfo;
import org.dwpd.domain.message.Weight;
import org.dwpd.service.message.SearchService;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.List;

import static java.util.stream.Collectors.toList;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private SearchMapper searchMapper;

    @Autowired
    private RestHighLevelClient client;

    @Override
    public Result queryMessage(String query) {

        log.info("搜索引擎查询，关键字：" + query);

        // 1. 对查询字段进行分词
        List<Term> terms = ToAnalysis.parse(query).getTerms();

        // 2. 根据分词结果构建倒排索引
        Map<Integer, Integer> weightMap = new ConcurrentHashMap<>();
        ExecutorService executor = Executors.newFixedThreadPool(20); // 创建一个拥有20个线程的线程池
        for (Term term : terms) {
            String word = term.getName();
            executor.submit(() -> processTerm(word, weightMap)); // 为每个term提交一个任务到线程池
        }
        executor.shutdown();
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 3. 通过权重类构建权值列表
        List<Weight> weightList = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : weightMap.entrySet()) {
            Weight weight = new Weight();
            weight.setMessageId(entry.getKey());
            weight.setWeight(entry.getValue());
            weightList.add(weight);
        }

        // 4. 针对触发的结果按照权重降序排序
        weightList.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o2.getWeight() - o1.getWeight();
            }
        });
        log.info("权重表：{}", weightList);

        // 5.通过正排索引获取数据并返回查询结果
        List<Integer> ids = new ArrayList<>();
        for (Weight weight : weightList) {
            ids.add(weight.getMessageId());
        }
        List<Message> result = new ArrayList<>();
        for (int id : ids) {
            Message message = searchMapper.selectMessageById(id);
            if (message == null) {
                continue;
            }
            result.add(message);
        }
        if (result.isEmpty()) {
            return Result.fail(Code.RESULT_CODE_FAIL, "查询失败");
        }
        return Result.success(result);
    }

    @Override
    public Result searchMessages(RequestParams params) {

        try {
            // 1. 准备Request对象
            SearchRequest request = new SearchRequest("message");
            // 2. 准备DSL
            // 2.1 构建BooleanQuery
            buildBasicQuery(params, request);
            // 2.2 分页
            request.source().from((params.getPage() - 1) * params.getSize()).size(params.getSize());
            // 3. 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析结果并返回
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public Result getFilters(RequestParams params) {
        try {
            // 1. 准备Request对象
            SearchRequest request = new SearchRequest("message");
            // 2 准备DSL
            // 2.1 query查询
            buildBasicQuery(params, request);
            // 2.2 设置size
            request.source().size(0); // 表示不接收文档
            // 2.3 聚合
            buildAggregation(request);
            // 3. 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析结果并返回
            return getParseResult(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Result getSuggestions(String prefix) {
        try {
            // 1. 准备Request对象
            SearchRequest request = new SearchRequest("message");
            // 2. 准备DSL
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "suggestions",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(prefix)
                            .skipDuplicates(true)
                            .size(10)
            ));
            // 3. 发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析结果
            Suggest suggest = response.getSuggest();
            // 4.1 根据补全查询名称，获取补全结果
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            // 4.2 获取options
            List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
            // 4.3 解析并返回结果
            return Result.success(options.stream().map(option -> option.getText().toString()).collect(toList()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void buildAggregation(SearchRequest request) {
        request.source().aggregation(AggregationBuilders
                .terms("startTimeAgg") // 自定义
                .field("startTime") // 根据出发时间聚合分类
                .size(10) // 最多返回十条数据
        );
        request.source().aggregation(AggregationBuilders
                .terms("peopleNumAgg") // 自定义
                .field("peopleNum") // 根据出发人数聚合分类
                .size(10) // 最多返回十条数据
        );
    }

    private static Result getParseResult(SearchResponse response) {
        Map<String, List<String>> result = new HashMap<>();
        Aggregations aggregations = response.getAggregations();
        List<String> startTimeName = getAggregationsByName(aggregations, "startTimeAgg");
        result.put("startTime", startTimeName);
        List<String> peopleNumName = getAggregationsByName(aggregations, "peopleNumAgg");
        result.put("peopleNum", peopleNumName);
        return Result.success(result);
    }

    private static List<String> getAggregationsByName(Aggregations aggregations, String aggName) {
        // 4.1 根据聚合名称获取聚合结果
        Terms terms = aggregations.get(aggName);
        // 4.2 获取buckets桶
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }

    private Result handleResponse(SearchResponse response) {
        // 4. 解析结果
        // 4.1 获取总条数
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        // 4.2 文档数组
        SearchHit[] hits = searchHits.getHits();
        // 4.3 返回结果列表
        List<MessageEsInfo> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            // 获取文档Source
            String json = hit.getSourceAsString();
            // 反序列化
            MessageEsInfo messageEsInfo = JSON.parseObject(json, MessageEsInfo.class);
            // 将结果添加到数组中
            list.add(messageEsInfo);
        }
        // 封装结果返回
        return Result.success(new PageResult(total, list));
    }

    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 1. 原始查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 关键字搜索
        String key = params.getKey();
        if (key == null || key.equals("")) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }
        // 条件判断
        // 出发地
        if (params.getStartPoint() != null && !params.getStartPoint().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("startPoint", params.getStartPoint()));
        }
        // 目的地
        if (params.getEndPoint() != null && !params.getEndPoint().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("endPoint", params.getEndPoint()));
        }
        // 出发时间
        if (params.getMaxTime() != null && !params.getMaxTime().equals("") && params.getMinTime() != null && !params.getMinTime().equals("")) {
//            boolQuery.filter(QueryBuilders.termQuery("startTime", params.getStartTime()));
            boolQuery.filter(QueryBuilders.rangeQuery("startTime").gte(params.getMinTime()).lte(params.getMaxTime()));
        }
        // 人数
        if (params.getPeopleNum() != null && params.getPeopleNum() >= 1) {
            boolQuery.filter(QueryBuilders.termQuery("peopleNum", params.getPeopleNum()));
        }
        // 是否上锁
        if (params.getLockFlag() != null && params.getLockFlag() >= 0) {
            boolQuery.filter(QueryBuilders.termQuery("lockFlag", params.getLockFlag()));
        }
        // 最新发布
        if (params.getSortBy() != null && !params.getSortBy().equals("")) {
            request.source().sort("createTime", SortOrder.DESC);
        }
        else {
            // 非按时间排序则按用户等级排序(降序)
            request.source().sort("level", SortOrder.DESC);
        }
//        boolQuery.filter(QueryBuilders.termQuery("accomplishFlag", 0));
        // 2. 算分控制
        FunctionScoreQueryBuilder functionScoreQueryBuilder =
                QueryBuilders.functionScoreQuery(
                        // 原始查询
                        boolQuery,
                        // 相关性算分查询
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        }
                );
        // 将条件放入source
        request.source().query(functionScoreQueryBuilder);
    }

    // 根据关键字重要性添加权重  1. 出发地: 10  2. 目的地: 10  3. 人数: 1
//    private void addWeight(Map<Integer, Integer> weightMap, List<Integer> queryList, int weight) {
//        for (Integer messageId : queryList) {
//            if (weightMap.containsKey(messageId)) {
//                weightMap.put(messageId, weightMap.get(messageId) + weight);
//            }
//            else {
//                weightMap.put(messageId, weight);
//            }
//        }
//    }

    // 根据关键字重要性添加权重  1. 出发地: 10  2. 目的地: 10  3. 人数: 1
    private void processTerm(String word, Map<Integer, Integer> weightMap) {
        // 获取出发地信息索引
        List<Integer> queryStartPointIndex = searchMapper.selectStartPointByTerm(word);
        addWeight(queryStartPointIndex, weightMap, 10);

        // 获取目的地信息索引
        List<Integer> queryEndPointIndex = searchMapper.selectEndPointByTerm(word);
        addWeight(queryEndPointIndex, weightMap,10);

        // 获取需求人数索引
        List<Integer> queryPeopleNumIndex = searchMapper.selectPeopleNumByTerm(word);
        addWeight(queryPeopleNumIndex, weightMap,1);
    }

    private void addWeight(List<Integer> queryList, Map<Integer, Integer> weightMap, int weight) {
        for (Integer messageId : queryList) {
            weightMap.merge(messageId, weight, Integer::sum); // 使用 merge 方法来处理并发更新(若存在messageId则weight求和，否则直接设为weight)
        }
    }

}
